//
// reference: https://developer.bitcoin.org/devguide/transactions.html
//

use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};

use crate::{
    Hash256Bytes, PublicKeyHashBytes, Script, TransactionId, TransactionValue, script,
    transaction::TransactionError,
};

// 小额交易：1-3次确认（约10-30分钟）。
// 大额交易：6次确认（约1小时，行业标准）。
#[derive(Debug)]
pub enum TransactionState {
    NotCreated,
    Broadcasted, // 交易已广播到全网
    // IncludedInBlock,     // 矿工将交易打包进新生成的区块，并完成工作量证明（PoW）
    NConfirmations(i32), // 后续区块不断在包含该交易的区块上追加，每追加一个区块，确认数+1
    Confirmed,
}

impl std::fmt::Display for TransactionState {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let state_str = match self {
            TransactionState::NotCreated => "NotCreated".to_string(),
            TransactionState::Broadcasted => "Broadcasted".to_string(),
            // TransactionState::IncludedInBlock => write!(f, "IncludedInBlock"),
            TransactionState::NConfirmations(n) => format!("NConfirmations({})", n),
            TransactionState::Confirmed => "Confirmed".to_string(),
        };
        write!(f, "{}", state_str)
    }
}

//
// When building a transaction from an unspent transaction output (UTXO), there are two scripts we’re particularly interested in:
//      The unspent output script, call it OS.
//      The spending input script, call it IS.
// The problem is producing such an IS that the concatenated script VS = IS + OS (the validation script),
// after being executed, returns a non-zero value.
//

//
// An input of a transaction.  It contains the location of the previous
// transaction's output that it claims and a signature that matches the
// output's public key.
//

// Signature script
//  ScriptSig
//      Data generated by a spender which is almost always used as variables to satisfy a pubkey script.
//      Signature Scripts are called scriptSig in code.
//  Not to be confused with: ECDSA signature (a signature, which can be used as part of a pubkey script in addition to other data)

// Locktime itself is an unsigned 4-byte integer which can be parsed two ways:
//
// If less than 500 million, locktime is parsed as a block height.
// The transaction can be added to any block which has this height or higher.
//
// If greater than or equal to 500 million, locktime is parsed using the Unix epoch time format
// (the number of seconds elapsed since 1970-01-01T00:00 UTC—currently over 1.395 billion).
// The transaction can be added to any block whose block time is greater than the locktime.

// https://developer.bitcoin.org/devguide/transactions.html#locktime_parsing_rules
// Even today, setting all sequence numbers to 0xffffffff (the default in Bitcoin Core)
// can still disable the time lock, so if you want to use locktime,
// at least one input must have a sequence number below the maximum.
//
// Locktime itself is an unsigned 4-byte integer which can be parsed two ways:
//
//  If less than 500 million, locktime is parsed as a block height.
//  The transaction can be added to any block which has this height or higher.
//
//  If greater than or equal to 500 million, locktime is parsed using
//  the Unix epoch time format (the number of seconds elapsed since
//  1970-01-01T00:00 UTC—currently over 1.395 billion).
//  The transaction can be added to any block whose block time is greater than
//  the locktime.

/// blockchain transaction
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
pub struct Transaction {
    version: u32,
    inputs: Vec<TransactionIn>,
    outputs: Vec<TransactionOut>,
    // The locktime indicates the earliest time a transaction can be added to the block chain.
    lock_time: u32,
}

impl Transaction {
    pub fn new(
        version: u32,
        inputs: Vec<TransactionIn>,
        outputs: Vec<TransactionOut>,
        lock_time: u32,
    ) -> Self {
        Self {
            version,
            inputs,
            outputs,
            lock_time,
        }
    }

    pub fn calc_tx_id(&self) -> TransactionId {
        let bytes = bincode::serialize(self).unwrap();
        let hash = Sha256::digest(&bytes);
        let hash_bytes: [u8; 32] = hash.into();
        TransactionId::new(hash_bytes)
    }

    pub fn convert_key_value(&self) -> (TransactionId, Vec<u8>) {
        let bytes = bincode::serialize(self).unwrap();
        let hash = Sha256::digest(&bytes);
        let hash_bytes: [u8; 32] = hash.into();
        (TransactionId::new(hash_bytes), bytes)
    }

    pub fn get_input_script(&self, index: u32) -> Script {
        self.inputs[index as usize].script_signature.clone()
    }

    pub fn get_inputs(&self) -> &Vec<TransactionIn> {
        &self.inputs
    }

    pub fn get_outputs(&self) -> &Vec<TransactionOut> {
        &self.outputs
    }

    pub fn get_output_script(&self, index: u32) -> Script {
        self.outputs[index as usize].script_pubkey.clone()
    }

    pub fn get_output_value(&self) -> TransactionValue {
        let out_value: TransactionValue = self.outputs.iter().map(|tx_in| tx_in.value).sum();

        out_value
    }

    pub fn get_timestamp(&self) -> u64 {
        self.lock_time as u64
    }

    pub fn is_coinbase(&self) -> bool {
        self.inputs.len() == 1 && self.inputs[0].prev_out_point.is_null()
    }

    pub fn preliminary_check(&self) -> Result<(), TransactionError> {
        if self.inputs.is_empty() || self.outputs.is_empty() {
            return Err(TransactionError::TxInOrTxOutEmpty);
        }

        for tx_out in &self.outputs {
            if tx_out.value < 0 {
                return Err(TransactionError::TxOutValueNegative);
            }
        }

        if self.is_coinbase() {
            let sig_size = self.inputs[0].script_signature.size();
            if !(2..=100).contains(&sig_size) {
                return Err(TransactionError::CoinbaseScriptSizeInvalid);
            }
        } else {
            for tx_in in &self.inputs {
                if tx_in.prev_out_point.is_null() {
                    return Err(TransactionError::TxInOutPointNull);
                }
            }
        }
        Ok(())
    }

    pub fn is_final(&self, block_time: i64, best_hight: u32) -> bool {
        if self.lock_time == 0 {
            return true;
        }

        let t = if self.lock_time < 500_000_000 {
            best_hight
        } else {
            block_time as u32
        };

        if self.lock_time < t {
            return true;
        }

        for tx_in in &self.inputs {
            if !tx_in.is_final() {
                return false;
            }
        }
        true
    }

    // bool IsNewerThan(const CTransaction& old) const {
    pub fn is_newer_than(&self, other: &Transaction) -> bool {
        if self.inputs.len() != other.inputs.len() {
            return false;
        }

        for (tx_in_self, tx_in_other) in self.inputs.iter().zip(other.inputs.iter()) {
            if tx_in_self.prev_out_point != tx_in_other.prev_out_point {
                return false;
            }
        }

        let mut is_newer = false;
        let mut lowest = u32::MAX;
        for (tx_in_self, tx_in_other) in self.inputs.iter().zip(other.inputs.iter()) {
            if tx_in_self.get_sequence() != tx_in_other.get_sequence() {
                if tx_in_self.get_sequence() <= lowest {
                    is_newer = false;
                    lowest = tx_in_self.get_sequence();
                }

                if tx_in_other.get_sequence() < lowest {
                    is_newer = true;
                    lowest = tx_in_other.get_sequence();
                }
            }
        }

        is_newer
    }

    pub fn serialize(&self) -> Vec<u8> {
        bincode::serialize(&self).unwrap()
    }

    pub fn deserialize(bytes: &[u8]) -> Result<Transaction, TransactionError> {
        let r = bincode::deserialize(bytes)
            .map_err(|e| TransactionError::DeserializationFailed(e.to_string()))?;
        Ok(r)
    }
}

/// transaction input
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
pub struct TransactionIn {
    prev_out_point: TransactionOutPoint,
    script_signature: Script, // typical P2PKH signature script: [signature], [public_key]
    sequence: u32,            // defautl value is 0xFFFF_FFFF
}

impl TransactionIn {
    pub fn new(out_point: TransactionOutPoint, script_signature: Script) -> Self {
        Self {
            prev_out_point: out_point,
            script_signature,
            sequence: u32::MAX,
        }
    }

    pub fn new_with_sequence(
        out_point: TransactionOutPoint,
        script_signature: Script,
        sequence: u32,
    ) -> Self {
        Self {
            prev_out_point: out_point,
            script_signature,
            sequence,
        }
    }

    fn is_final(&self) -> bool {
        self.sequence == u32::MAX
    }

    pub fn get_script_signature(&self) -> &Script {
        &self.script_signature
    }

    pub fn get_prev_out_point(&self) -> &TransactionOutPoint {
        &self.prev_out_point
    }

    fn get_sequence(&self) -> u32 {
        self.sequence
    }
}

// Pubkey script
//  ScriptPubKey
//      A script included in outputs which sets the conditions that must be fulfilled for those satoshis to be spent.
//      Data for fulfilling the conditions can be provided in a signature script.
//      Pubkey Scripts are called a scriptPubKey in code.
//
//  Not to be confused with: Pubkey (a public key, which can be used as part of a pubkey script but don’t provide a programmable authentication mechanism),
//      Signature script (a script that provides data to the pubkey script)

/// transaction output
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
pub struct TransactionOut {
    value: TransactionValue,
    script_pubkey: Script, // typical P2PKH pubkey script:  OP_DUP, OP_HASH160, [public_key_hash160], OP_EQUALVERIFY, OP_CHECKSIG
}

impl TransactionOut {
    pub fn new(value: TransactionValue, script_pubkey: Script) -> Self {
        Self {
            value,
            script_pubkey,
        }
    }

    pub fn get_value(&self) -> TransactionValue {
        self.value
    }

    pub fn get_payee_pubkey_hash(&self) -> Result<PublicKeyHashBytes, TransactionError> {
        match script::get_transaction_receiver(&self.script_pubkey) {
            Some(pk_hash) => Ok(pk_hash),
            None => Err(TransactionError::CanNotFindPublicKeyHashInScript),
        }
    }

    pub fn get_script_pubkey(&self) -> &Script {
        &self.script_pubkey
    }
}

#[derive(Debug)]
pub struct TransactionInPoint(TransactionOutPoint);

impl TransactionInPoint {
    pub fn new(tx_id: TransactionId, index: u32) -> Self {
        Self(TransactionOutPoint::new(tx_id, index))
    }

    pub fn get_tx_id(&self) -> &TransactionId {
        self.0.get_tx_id()
    }
}

/// transaction output point
#[derive(Serialize, Deserialize, PartialEq, Eq, Hash, Debug, Clone, Copy)]
pub struct TransactionOutPoint {
    tx_id: TransactionId,
    index: u32,
}

impl TransactionOutPoint {
    pub fn new(tx_id: TransactionId, index: u32) -> Self {
        Self { tx_id, index }
    }

    pub fn new_raw(tx_id: Hash256Bytes, index: u32) -> Self {
        Self::new(TransactionId::new(tx_id), index)
    }

    pub fn new_null() -> Self {
        Self {
            tx_id: TransactionId::new([0; 32]),
            index: u32::MAX,
        }
    }

    fn is_null(&self) -> bool {
        self.tx_id == TransactionId::new([0; 32]) && self.index == u32::MAX
    }

    pub fn get_tx_id(&self) -> &TransactionId {
        &self.tx_id
    }

    pub fn get_index(&self) -> u32 {
        self.index
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::OpCode;
    use crate::{CompactSignatureBytes, PublicKeyBytes};

    #[rustfmt::skip]
    const TX_ID_BYTES: [u8; 32] = [
        1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
        17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32,
    ];
    const PUBLIC_KEY_HASH_BYTES: [u8; 20] = [10; 20];
    const SIGNATURE_BYTES: [u8; 64] = [11; 64];
    const PUBLIC_KEY_BYTES: [u8; 33] = [12; 33];

    fn new_tx_out_point() -> TransactionOutPoint {
        let tx_id = TransactionId::new(TX_ID_BYTES);
        let index = 100u32;
        TransactionOutPoint::new(tx_id, index)
    }

    fn new_tx_out_point_bytes() -> Vec<u8> {
        let mut vec = TX_ID_BYTES.to_vec();
        vec.extend(vec![100, 0, 0, 0]);
        vec
    }

    fn new_tx_out() -> TransactionOut {
        let value = 9;
        let pub_key_hash = PublicKeyHashBytes::new(PUBLIC_KEY_HASH_BYTES);
        let script_pubkey = Script::new(vec![
            OpCode::OpDup,
            OpCode::OpHash160,
            OpCode::OpPubKeyHash(pub_key_hash),
        ]);
        TransactionOut::new(value, script_pubkey)
    }

    fn new_tx_out_bytes() -> Vec<u8> {
        #[rustfmt::skip]
        let mut vec = vec![
            9, 0, 0, 0, 0, 0, 0, 0, // value: u64
            3, 0, 0, 0, 0, 0, 0, 0, // size of script_pubkey
            0, 0, 0, 0, // first opcode
            1, 0, 0, 0, // second opcode
            7, 0, 0, 0,
        ];
        vec.extend(PUBLIC_KEY_HASH_BYTES);
        vec
    }
    fn new_tx_in() -> TransactionIn {
        let out_point = new_tx_out_point();
        let script_signature = Script::new(vec![
            OpCode::OpSignature(CompactSignatureBytes::new(SIGNATURE_BYTES)),
            OpCode::OpPubKey(PublicKeyBytes::new(PUBLIC_KEY_BYTES)),
        ]);
        let sequence = 3;
        TransactionIn::new_with_sequence(out_point, script_signature, sequence)
    }

    fn new_tx_in_bytes() -> Vec<u8> {
        let mut out_point_vec = TX_ID_BYTES.to_vec();
        out_point_vec.extend(vec![100, 0, 0, 0]);

        let mut sig_vec = vec![
            5, 0, 0, 0, // id of OpCode::OpSignature
        ];
        sig_vec.extend(SIGNATURE_BYTES);

        let mut pub_key_vec = vec![
            6, 0, 0, 0, // id of OpCode::OpPubKey
        ];
        pub_key_vec.extend(PUBLIC_KEY_BYTES);

        let mut vec = Vec::new();
        vec.extend(out_point_vec);
        vec.extend(vec![2, 0, 0, 0, 0, 0, 0, 0]); // size of script_signature
        vec.extend(sig_vec);
        vec.extend(pub_key_vec);
        vec.extend(vec![3, 0, 0, 0]); // sequence
        vec
    }

    fn new_tx() -> Transaction {
        let version = 123;
        let inputs = vec![new_tx_in()];
        let outputs = vec![new_tx_out()];
        let lock_time = 0;
        Transaction::new(version, inputs, outputs, lock_time)
    }

    fn new_tx_bytes() -> Vec<u8> {
        let mut vec: Vec<u8> = vec![
            123, 0, 0, 0, // version
            1, 0, 0, 0, 0, 0, 0, 0, // size of inputs
        ];

        vec.extend(new_tx_in_bytes());
        vec.extend([1, 0, 0, 0, 0, 0, 0, 0]); // size of outputs
        vec.extend(new_tx_out_bytes());
        vec.extend([0, 0, 0, 0]); // lock_time
        vec
    }

    #[test]
    fn test_serde_bincode_tx_out_point() {
        let outpoint = new_tx_out_point();
        let vec = new_tx_out_point_bytes();
        assert_eq!(bincode::serialize(&outpoint).unwrap(), vec);
        assert_eq!(
            bincode::deserialize::<TransactionOutPoint>(&vec).unwrap(),
            outpoint
        );
    }

    #[test]
    fn test_serde_bincode_tx_out() {
        let txout = new_tx_out();
        let vec = new_tx_out_bytes();
        assert_eq!(bincode::serialize(&txout).unwrap(), vec);
        assert_eq!(bincode::deserialize::<TransactionOut>(&vec).unwrap(), txout);
    }

    #[test]
    fn test_serde_bincode_tx_in() {
        let txin = new_tx_in();
        let vec = new_tx_in_bytes();
        assert_eq!(bincode::serialize(&txin).unwrap(), vec);
        assert_eq!(bincode::deserialize::<TransactionIn>(&vec).unwrap(), txin);
    }

    #[test]
    fn test_serde_bincode_tx() {
        let tx = new_tx();
        let vec = new_tx_bytes();
        assert_eq!(bincode::serialize(&tx).unwrap(), vec);
        assert_eq!(bincode::deserialize::<Transaction>(&vec).unwrap(), tx);
    }

    #[test]
    fn test_serde_bincode_tx_size() {
        let bytes_of_usize = std::mem::size_of::<usize>();
        let tx_ser_size = 193 + 4 * bytes_of_usize;
        let tx = new_tx();
        let tx_ser = bincode::serialize(&tx).unwrap();
        assert_eq!(tx_ser.len(), tx_ser_size);

        let mut vec = vec![tx];
        assert_eq!(
            bincode::serialize(&vec).unwrap().len(),
            tx_ser_size + bytes_of_usize
        );
        vec.push(new_tx());

        let vec_ser = bincode::serialize(&vec).unwrap();
        assert_eq!(vec_ser.len(), 2 * tx_ser_size + bytes_of_usize);
        assert_eq!(
            bincode::deserialize::<Vec<Transaction>>(&vec_ser).unwrap(),
            vec
        );
    }

    #[test]
    fn test_serde_bincode_tx_split() {
        let bytes_of_usize = std::mem::size_of::<usize>();

        let tx0 = new_tx();
        let tx0_ser = bincode::serialize(&tx0).unwrap();
        let tx0_ser_size = tx0_ser.len();

        let tx1 = new_tx();
        let tx1_ser = bincode::serialize(&tx1).unwrap();
        let tx1_ser_size = tx1_ser.len();

        let cnt: usize = 2;
        let mut test_vec = bincode::serialize(&cnt).unwrap();
        assert_eq!(test_vec.len(), bytes_of_usize);
        test_vec.extend(tx0_ser);
        test_vec.extend(tx1_ser);
        assert_eq!(test_vec.len(), tx0_ser_size + tx1_ser_size + bytes_of_usize);

        let txs_ser = vec![tx0, tx1];
        assert_eq!(bincode::serialize(&txs_ser).unwrap(), test_vec)
    }
}

#[cfg(test)]
mod tests_learning {
    use super::*;

    #[test]
    fn tx_type_size() {
        assert_eq!(24, std::mem::size_of::<Vec<TransactionIn>>());
        assert_eq!(24, std::mem::size_of::<Vec<TransactionOut>>());
        assert_eq!(4 + 24 + 24 + 4, std::mem::size_of::<Transaction>());
    }

    #[test]
    fn empyt_tx_serailize_size() {
        let inputs: Vec<TransactionIn> = vec![];
        let outputs: Vec<TransactionOut> = vec![];

        assert_eq!(8, bincode::serialize(&inputs).unwrap().len());
        assert_eq!(8, bincode::serialize(&outputs).unwrap().len());

        let tx = Transaction {
            version: 123,
            inputs,
            outputs,
            lock_time: 0,
        };

        assert_eq!(bincode::serialize(&tx).unwrap().len(), 4 + 8 + 8 + 4);
    }
}
